
//////////////////////////////////////////////////////////////////////////
// Flag Operation
void
CCpu::SET_FLAG(u8 flag)
{
  m_Register.F |= flag;
}

void
CCpu::CLEAR_FLAG(u8 flag)
{
  m_Register.F &= ~flag;
}

u8
CCpu::CHECK_FLAG(u8 flag)
{
  return m_Register.F & flag;
}

void
CCpu::SET_FLAG_CONDITION(bool c, u8 flag)
{
  m_Register.F &= ~flag;
  if (c) {
    m_Register.F |= flag;
  }
}

void
CCpu::SET_ZN_FLAG(u8 value)
{
  m_Register.F &= ~(Z_FLAG | N_FLAG);
  m_Register.F |= (m_ZNTable[value]);
}

//////////////////////////////////////////////////////////////////////////
// ADDRESSING MODES
u16
CCpu::GET_ADDR_IMMEDIATE()
{
  return m_Register.PC++;
}
u16
CCpu::GET_ADDR_ZEROPAGE()
{
  return ReadByte(m_Register.PC++);
}
u16
CCpu::GET_ADDR_ABSOLUTE(bool jsr)
{
  u16 addr = ReadWord(m_Register.PC++);
  if (!jsr) {
    m_Register.PC++;
  }
  return addr;
}
u16
CCpu::GET_ADDR_ZEROPAGE_X()
{
  u16 addr = ReadByte(m_Register.PC++);
  addr += m_Register.X;
  return addr & 0xFF;
}
u16
CCpu::GET_ADDR_ZEROPAGE_Y()
{
  u16 addr = ReadByte(m_Register.PC++);
  addr += m_Register.Y;
  return addr & 0xFF;
}
u16
CCpu::GET_ADDR_ABSOLUTE_X()
{
  u16 addr = ReadWord(m_Register.PC);
  m_Register.PC += 2;
  u16 addr1 = addr + (u16)m_Register.X;
  if ((addr&0x100) != (addr1&0x100)) {
    m_ExtraCycle = 1;
  }
  return addr1;
}
u16
CCpu::GET_ADDR_ABSOLUTE_Y()
{
  u16 addr = ReadWord(m_Register.PC);
  m_Register.PC += 2;
  u16 addr1 = addr + (u16)m_Register.Y;
  if ((addr&0x100) != (addr1&0x100)) {
    m_ExtraCycle = 1;
  }
  return addr1;
}
u16
CCpu::GET_ADDR_PREINDEXED()
{
  u16 addr = ReadByte(m_Register.PC++);
  return ReadWordZeroPage(addr + m_Register.X);
}
u16
CCpu::GET_ADDR_POSTINDEXED()
{
  u16 addr = ReadByte(m_Register.PC++);
  addr = ReadWordZeroPage((u8)addr);
  u16 addr1 = addr + m_Register.Y;
  if ((addr&0x100) != (addr1&0x100)) {
    m_ExtraCycle = 1;
  }
  return addr1;
}
u8
CCpu::GET_DATA_IMMEDIATE()
{
  return ReadByte(GET_ADDR_IMMEDIATE());
}
u8
CCpu::GET_DATA_ZEROPAGE()
{
  return ReadByte((u8)GET_ADDR_ZEROPAGE());
}
u8
CCpu::GET_DATA_ABSOLUTE(bool jsr)
{
  return ReadByte(GET_ADDR_ABSOLUTE(jsr));
}
u8
CCpu::GET_DATA_ZEROPAGE_X()
{
  return ReadByte(GET_ADDR_ZEROPAGE_X());
}
u8
CCpu::GET_DATA_ZEROPAGE_Y()
{
  return ReadByte(GET_ADDR_ZEROPAGE_Y());
}
u8
CCpu::GET_DATA_ABSOLUTE_X()
{
  return ReadByte(GET_ADDR_ABSOLUTE_X());
}
u8
CCpu::GET_DATA_ABSOLUTE_Y()
{
  return ReadByte(GET_ADDR_ABSOLUTE_Y());
}
u8
CCpu::GET_DATA_PREINDEXED()
{
  return ReadByte(GET_ADDR_PREINDEXED());
}
u8
CCpu::GET_DATA_POSTINDEXED()
{
  return ReadByte(GET_ADDR_POSTINDEXED());
}

//////////////////////////////////////////////////////////////////////////
// Stack Operation
void
CCpu::Push(u8 data)
{
  m_Stack[(m_Register.S--) & 0xFF] = (data);
}

void
CCpu::PushWord(u16 data)
{
  Push(data >> 8);
  Push(data & 0xFF);
}

u8
CCpu::Pop()
{
  return m_Stack[(++m_Register.S) & 0xFF];
}

u16
CCpu::PopWord()
{
  u16 data= Pop();
  data |= ((u16) Pop() << 8);
  return data;
}

//////////////////////////////////////////////////////////////////////////
// Instructions
void
CCpu::ADC(u8 data)
{
  u16 result  = m_Register.A + data + (m_Register.F&C_FLAG);
  u8 result_l = (u8) result;
  u8 overflow = ((~(m_Register.A ^ data)) & (m_Register.A ^ result_l) & 0x80);
  SET_FLAG_CONDITION((overflow != 0), V_FLAG);
  SET_FLAG_CONDITION((result & 0xFF00) != 0, C_FLAG);
  SET_ZN_FLAG(m_Register.A);
  m_Register.A = result_l;
}

void
CCpu::AND(u8 data)
{
  m_Register.A &= data;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::ASL(u16 addr)
{
  u8 data = ReadByte(addr);
  SET_ZN_FLAG(data);
  SET_FLAG_CONDITION((data & 0x80) != 0, C_FLAG);
  data <<= 1;
  Write(addr, data);
}

void
CCpu::ASL_A()
{
  SET_FLAG_CONDITION((m_Register.A & 0x80) != 0, C_FLAG);
  m_Register.A <<= 1;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::BCC()
{
  JMP_IF(!CHECK_FLAG(C_FLAG));
}

void
CCpu::BCS()
{
  JMP_IF(CHECK_FLAG(C_FLAG));
}

void
CCpu::BEQ()
{
  JMP_IF(CHECK_FLAG(Z_FLAG));
}

void
CCpu::BIT(u8 data)
{
  SET_FLAG_CONDITION((data & m_Register.A) == 0, Z_FLAG);
  SET_FLAG_CONDITION((data & 0x80) != 0, N_FLAG);
  SET_FLAG_CONDITION((data & 0x40) != 0, V_FLAG);
}

void
CCpu::BMI()
{
  JMP_IF(CHECK_FLAG(N_FLAG));
}

void
CCpu::BNE()
{
  JMP_IF(!CHECK_FLAG(Z_FLAG));
}

void
CCpu::BPL()
{
  JMP_IF(!CHECK_FLAG(N_FLAG));
}

void
CCpu::BRK()
{
  m_Register.PC++;
  PushWord(m_Register.PC);
  SET_FLAG(B_FLAG);
  Push(m_Register.F);
  SET_FLAG(I_FLAG);
  m_Register.PC = ReadWord(IRQ_VECTOR);
}

void
CCpu::BVC()
{
  JMP_IF(!CHECK_FLAG(V_FLAG));
}

void
CCpu::BVS()
{
  JMP_IF(CHECK_FLAG(V_FLAG));
}

void
CCpu::CMP(u8 data)
{
  u16 result= (u16) m_Register.A - (u16) data;
  SET_FLAG_CONDITION((result & 0x8000) == 0, C_FLAG);
  SET_ZN_FLAG((u8) result);
}

void
CCpu::CPX(u8 data)
{
  u16 result= (u16) m_Register.X - (u16) data;
  SET_FLAG_CONDITION((result & 0x8000) == 0, C_FLAG);
  SET_ZN_FLAG((u8) result);
}

void
CCpu::CPY(u8 data)
{
  u16 result= (u16) m_Register.Y - (u16) data;
  SET_FLAG_CONDITION((result & 0x8000) == 0, C_FLAG);
  SET_ZN_FLAG((u8) result);
}

void
CCpu::DEC(u16 addr)
{
  u8 data = ReadByte(addr) - 1;
  Write(addr, data);
  SET_ZN_FLAG(data);
}

void
CCpu::DEX()
{
  m_Register.X--;
  SET_ZN_FLAG(m_Register.X);
}

void
CCpu::DEY()
{
  m_Register.Y--;
  SET_ZN_FLAG(m_Register.Y);
}

void
CCpu::EOR(u8 data)
{
  m_Register.A ^= data;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::INC(u16 addr)
{
  u8 data = ReadByte(addr) + 1;
  Write(addr, data);
  SET_ZN_FLAG(data);
}

void
CCpu::INX()
{
  m_Register.X++;
  SET_ZN_FLAG(m_Register.X);
}

void
CCpu::INY()
{
  m_Register.Y++;
  SET_ZN_FLAG(m_Register.Y);
}

void
CCpu::JMP(u16 addr)
{
  m_Register.PC = addr;
}

void
CCpu::JSR()
{
  u16 addr= GET_ADDR_ABSOLUTE(true);
  PushWord(m_Register.PC);
  m_Register.PC = addr;
}

void
CCpu::LDA(u8 data)
{
  m_Register.A = data;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::LDX(u8 data)
{
  m_Register.X = data;
  SET_ZN_FLAG(m_Register.X);
}

void
CCpu::LDY(u8 data)
{
  m_Register.Y = data;
  SET_ZN_FLAG(m_Register.Y);
}

void
CCpu::LSR_A()
{
  u16 d_16= m_Register.A;
  SET_FLAG_CONDITION((d_16 & 1) != 0, C_FLAG);
  m_Register.A >>= 1;
  SET_ZN_FLAG(m_Register.A);
  CLEAR_FLAG(N_FLAG);
}

void
CCpu::LSR(u16 addr)
{
  u16 data= ReadByte(addr);
  SET_FLAG_CONDITION((data & 1) != 0, C_FLAG);
  data >>= 1;
  SET_ZN_FLAG((u8) data);
  CLEAR_FLAG(N_FLAG);
  Write(addr, (u8) data);
}

void
CCpu::ORA(u8 data)
{
  m_Register.A |= data;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::PHA()
{
  Push(m_Register.A);
}

void
CCpu::PHP()
{
  Push(m_Register.F);
}

void
CCpu::PLA()
{
  m_Register.A = Pop();
}

void
CCpu::PLP()
{
  m_Register.F = Pop();
}

void
CCpu::ROL_A()
{
  u8 carry= (m_Register.F& C_FLAG) ? 1 : 0;
  SET_FLAG_CONDITION((m_Register.A & 0x80) != 0, C_FLAG);
  m_Register.A <<= 1;
  m_Register.A |= carry;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::ROL(u16 addr)
{
  u8 data = ReadByte(addr);
  u8 carry= (m_Register.F& C_FLAG) ? 1 : 0;
  SET_FLAG_CONDITION((data & 0x80) != 0, C_FLAG);
  data <<= 1;
  data |= carry;
  SET_ZN_FLAG(data);
  Write(addr, data);
}

void
CCpu::ROR_A()
{
  u8 carry= (m_Register.F& C_FLAG) ? 0x80 : 0;
  SET_FLAG_CONDITION((m_Register.A & 1) != 0, C_FLAG);
  m_Register.A >>= 1;
  m_Register.A |= carry;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::ROR(u16 addr)
{
  u8 data = ReadByte(addr);
  u8 carry= (m_Register.F& C_FLAG) ? 0x80 : 0;
  SET_FLAG_CONDITION((data & 1) != 0, C_FLAG);
  data >>= 1;
  data |= carry;
  SET_ZN_FLAG(data);
  Write(addr, data);
}

void
CCpu::RTI()
{
  m_Register.F = Pop();
  SET_FLAG(R_FLAG);
  m_Register.PC = PopWord();
}

void
CCpu::RTS()
{
  m_Register.PC = PopWord() + 1;
}

void
CCpu::SBC(u8 data)
{
  u8 carry    = (m_Register.F& C_FLAG) ? 1 : 0;
  u16 result  = m_Register.A - data - 1 + carry;
  u8 result_l = (u8) result;
  u8 overflow = ((m_Register.A ^ data) & (m_Register.A ^ result_l) & 0x80);
  SET_FLAG_CONDITION((overflow != 0), V_FLAG);
  SET_FLAG_CONDITION((result & 0xFF00) == 0, C_FLAG);
  SET_ZN_FLAG(result_l);
  m_Register.A = result_l;
}

void
CCpu::STA(u16 addr)
{
  Write(addr, m_Register.A);
}

void
CCpu::STX(u16 addr)
{
  Write(addr, m_Register.X);
}

void
CCpu::STY(u16 addr)
{
  Write(addr, m_Register.Y);
}

void
CCpu::TAX()
{
  m_Register.X = m_Register.A;
  SET_ZN_FLAG(m_Register.X);
}

void
CCpu::TAY()
{
  m_Register.Y = m_Register.A;
  SET_ZN_FLAG(m_Register.Y);
}

void
CCpu::TSX()
{
  m_Register.X = m_Register.S;
  SET_ZN_FLAG(m_Register.X);
}

void
CCpu::TXA()
{
  m_Register.A = m_Register.X;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::TXS()
{
  m_Register.S = m_Register.X;
}

void
CCpu::TYA()
{
  m_Register.A = m_Register.Y;
  SET_ZN_FLAG(m_Register.A);
}

void
CCpu::JMP_IF(u8 c)
{
  if (c) {
    u16 orig_pc = m_Register.PC;
    m_Register.PC += (char)ReadByte(m_Register.PC);
    m_ExtraCycle = ((orig_pc&0x100) != (m_Register.PC&0x100))?2:1;
  }
  m_Register.PC++;
}

int
CCpu::NMI()
{
  PushWord(m_Register.PC);
  Push(m_Register.F);
  SET_FLAG(I_FLAG);
  CLEAR_FLAG(D_FLAG);
  m_Register.PC = ReadWord(NMI_VECTOR);
  return 7;
}
